;; Better defaults
;; Defer garbage collection further back in the startup process
(setq gc-cons-threshold most-positive-fixnum)
(setq read-process-output-max (* 16 1024 1024))
(setq initial-scratch-message nil)
(setq inhibit-splash-screen t)
(setq uniquify-buffer-name-style 'post-forward-angle-brackets) ; Show path if names are same
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
;; all backups goto ~/.backups instead in the current directory
(setq make-backup-files nil)
(setq ring-bell-function 'ignore)
(fset 'yes-or-no-p 'y-or-n-p)
(setq use-short-answers t)
(setq-default
 gdb-many-windows t
 ad-redefinition-action 'accept                  ; Silence warnings for redefinition
 cursor-in-non-selected-windows t                ; Hide the cursor in inactive windows
 display-time-default-load-average nil            ; Don't display load average
 fill-column 80                                  ; Set width for automatic line breaks
 help-window-select t                            ; Focus new help windows when opened
 indent-tabs-mode nil                            ; Prefer spaces over tabs
 inhibit-startup-screen t                        ; Disable start-up screen
 kill-ring-max 128                                ; Maximum length of kill ring
 load-prefer-newer t                              ; Prefer the newest version of a file
 mark-ring-max 128                                ; Maximum length of mark ring
 select-enable-clipboard t                        ; Merge system's and Emacs' clipboard
 user-full-name "ouyu"              ; Set the full name of the current user
 user-mail-address "ouyu@tp-link.com.hk"  ; Set the email address of the current user
 vc-follow-symlinks t                            ; Always follow the symlinks
 view-read-only t
 )                                ; Always open read-only buffers in view-mode

(global-hl-line-mode)                            ; Hightlight current line

(set-default-coding-systems 'utf-8)              ; Default to utf-8 encoding

;; Tab and Space
;; Permanently indent with spaces, never with TABs
(setq-default c-basic-offset   4
              tab-width        4
              indent-tabs-mode nil)

;; (load-theme 'wombat t)

(setq package-archives '(("gnu"    . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
                         ("nongnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/")
                         ("melpa"  . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")))

;; HACK: DO NOT save `package-selected-packages' to `custom-file'
;; @see https://github.com/jwiegley/use-package/issues/383#issuecomment-247801751
(defun my-package--save-selected-packages (&optional value)
  "Set `package-selected-packages' to VALUE but don't save to option `custom-file'."
  (when value
    (setq package-selected-packages value))
  (unless after-init-time
    (add-hook 'after-init-hook #'my-package--save-selected-packages)))
(advice-add 'package--save-selected-packages :override #'my-package--save-selected-packages)

;; Initialize packages
(unless (bound-and-true-p package--initialized) ; To avoid warnings in 27
  (setq package-enable-at-startup nil)          ; To prevent initializing twice
  (package-initialize))

;; More options
(setq package-install-upgrade-built-in t)

;; Setup `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; Should set before loading `use-package'
(setq use-package-always-ensure t
      use-package-always-defer t
      use-package-expand-minimally t
      use-package-enable-imenu-support t)

;; Required by `use-package'
(use-package diminish :ensure t)

;; Update GPG keyring for GNU ELPA
(use-package gnu-elpa-keyring-update)

;; Update packages
(unless (fboundp 'package-upgrade-all)
  (use-package auto-package-update
    :init
    (setq auto-package-update-delete-old-versions t
          auto-package-update-hide-results t)
    (defalias 'package-upgrade-all #'auto-package-update-now)))

(use-package undo-fu
  :config
  ;; Store more undo history to prevent loss of data
  (setq undo-limit 8000000
        undo-strong-limit 8000000
        undo-outer-limit 8000000))

;; c
(setq c-electric-pound-behavior (quote (alignleft)))
(electric-quote-mode)
(electric-layout-mode)
(electric-pair-mode)
;; Tip 1: use more popular style "linux" instead of "gnu"
(setq c-default-style '((java-mode . "java")
                        (awk-mode . "awk")
                        (other . "linux")))
;; give me NO newline automatically after electric expressions are entered
(setq c-auto-newline nil)
;; Browse Emacs' C code
(push '(nil "^DEFUN *(\"\\([a-zA-Z0-9-]+\\)" 1) imenu-generic-expression)
(setq cc-search-directories '("." "/usr/include" "/usr/local/include/*" "../*/include" "$WXWIN/include"))
(font-lock-add-keywords 'c-mode
                        '(
                          ("\\<\\(if\\) *(" 1 font-lock-keyword-face)
                          ("\\<\\(sizeof\\) *(" 1 font-lock-keyword-face)
                          ("\\<\\(typeof\\) *(" 1 font-lock-keyword-face)
                          ("\\<\\(for\\) *(" 1 font-lock-keyword-face)
                          ("\\<\\(while\\) *(" 1 font-lock-keyword-face)
                          ("\\<\\(switch\\) *(" 1 font-lock-keyword-face)
                          ("\\<[A-Z_]+\\>" 0 font-lock-variable-name-face)
                          ("\\<\\([a-z_A-Z]+[a-z_A-Z0-9]*\\) *(" 1 font-lock-function-name-face)
                          )
                        )

(font-lock-add-keywords 'c++-mode

                        '(

                          ("\\<\\(if\\) *(" 1 font-lock-keyword-face)

                          ("\\<\\(sizeof\\) *(" 1 font-lock-keyword-face)

                          ("\\<\\(typeof\\) *(" 1 font-lock-keyword-face)

                          ("\\<\\(for\\) *(" 1 font-lock-keyword-face)

                          ("\\<\\(while\\) *(" 1 font-lock-keyword-face)

                          ("\\<\\(switch\\) *(" 1 font-lock-keyword-face)

                          ("\\<[A-Z_]+\\>" 0 font-lock-variable-name-face)

                          ("\\<\\([a-z_A-Z]+[a-z_A-Z0-9]*\\) *(" 1 font-lock-function-name-face)

                          )

                        )
;;; Vim Bindings
(use-package evil
  :demand t
  :bind (("<escape>" . keyboard-escape-quit))
  :init
  ;; allows for using cgn
  ;; (setq evil-search-module 'evil-search)
  (setq evil-want-keybinding nil)
  ;; no vim insert bindings
  (setq evil-undo-system 'undo-fu)
    ;; evil 1.0.8 search word instead of symbol
  (setq evil-symbol-word-search t)
  ;; don't add replaced text to `kill-ring'
  (setq evil-kill-on-visual-paste nil)
  :config
  (evil-mode 1)
  (defalias 'forward-evil-word 'forward-evil-symbol)
  (defun my-evil-prev-line ()
    (interactive)
    (evil-previous-visual-line 5)
    )
  (defun my-evil-next-line ()
    (interactive)
    (evil-next-visual-line 5)
    )
  (evil-set-initial-state 'special-mode 'emacs)
  (evil-define-key '(normal visual) 'global (kbd "L") 'evil-end-of-line)
  (evil-define-key '(normal visual) 'global (kbd "H") 'evil-first-non-blank)
  (evil-define-key '(normal visual) 'global (kbd "C-k") 'my-evil-prev-line)
  (evil-define-key '(normal visual) 'global (kbd "C-j") 'my-evil-next-line)
  (evil-define-key '(normal visual) 'global (kbd "M-b") 'scroll-other-window-down)
  (evil-define-key '(normal visual) 'global (kbd "M-f") 'scroll-other-window)
  (evil-define-key '(normal visual) 'global (kbd "gd") 'xref-find-definitions)
  (evil-define-key '(normal visual) 'global (kbd "gh") 'eldoc-box-help-at-point)
  (evil-define-key '(normal visual) 'global (kbd "gD") 'xref-find-definitions-other-window)
  (evil-define-key '(normal visual) 'global (kbd "gr") 'xref-find-references)
  (evil-define-key '(normal) 'global (kbd "u") 'undo-fu-only-undo)
  (evil-define-key '(normal) 'global (kbd "C-r") 'undo-fu-only-redo)
    ;;; <tab> will fall back to company gtags when company common failed with setting this, dont know why
  (evil-define-key '(insert) 'global (kbd "C-i") 'company-gtags)
  (defvar my-initial-evil-state-setup
    '((minibuffer-inactive-mode . emacs)
      (calendar-mode . emacs)
      (special-mode . emacs)
      (grep-mode . emacs)
      (Info-mode . emacs)
      (term-mode . emacs)
      (sdcv-mode . emacs)
      (log-edit-mode . emacs)
      (vc-log-edit-mode . emacs)
      (magit-log-edit-mode . emacs)
      (erc-mode . emacs)
      (diff-mode . emacs)
      (ffip-diff-mode . normal)
      (neotree-mode . emacs)
      (gud-mode . emacs)
      (help-mode . emacs)
      (eshell-mode . emacs)
      (shell-mode . emacs)
      (vterm-mode . emacs)
      (xref--xref-buffer-mode . emacs)
      (epa-key-list-mode . emacs)
      (fundamental-mode . emacs)
      (woman-mode . emacs)
      (sr-mode . emacs)
      (profiler-report-mode . emacs)
      (dired-mode . emacs)
      (compilation-mode . emacs)
      (speedbar-mode . emacs)
      (ivy-occur-mode . emacs)
      (ffip-file-mode . emacs)
      (ivy-occur-grep-mode . normal)
      (messages-buffer-mode . normal)
      )
    "Default evil state per major mode.")
  ;; initial evil state per major mode
  (dolist (p my-initial-evil-state-setup)
    (evil-set-initial-state (car p) (cdr p)))

  )

;;; Vim Bindings Everywhere else
(use-package evil-collection
  :after evil
  :config
  (setq evil-want-integration t)
  (evil-collection-init))

(use-package evil-commentary
  :hook
  (after-init . evil-commentary-mode)
  )

(use-package doom-modeline
  :ensure t
  :config (unless (display-graphic-p) (setq doom-modeline-icon nil))
  :hook (after-init . doom-modeline-mode))

(use-package doom-themes
  :ensure t
  :config
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
        doom-themes-enable-italic t) ; if nil, italics is universally disabled

  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)
  ;; Enable custom neotree theme (all-the-icons must be installed!)
  (doom-themes-neotree-config)
  ;; or for treemacs users
  (setq doom-themes-treemacs-theme "doom-atom") ; use "doom-colors" for less minimal icon theme
  (doom-themes-treemacs-config)
  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

(use-package atom-one-dark-theme)

(load-theme 'atom-one-dark t)

(global-display-line-numbers-mode 1)

(use-package winum
  :config
  ;; (setq winum-format "[%s] ")
  ;; (setq winum-mode-line-position 0)
  ;; (set-face-attribute 'winum-face nil :foreground "DeepPink" :underline "DeepPink" :weight 'bold)
  ;; (set-face-attribute 'winum-face nil :foreground "#ff00" :weight 'bold)
  (winum-mode)
  )

(defun my-open-cfg ()
  "open file"
  (interactive)
  (find-file "~/.emacs.d/init.el")
  )

(use-package general
  :config
  (general-create-definer my-space-leader-def
    :prefix "SPC"
    :non-normal-prefix "C-c"
    :states '(normal visual insert)
    )
  (my-space-leader-def
    "0" 'winum-select-window-0-or-10
    "1" 'winum-select-window-1
    "2" 'winum-select-window-2
    "3" 'winum-select-window-3
    "4" 'winum-select-window-4
    "5" 'winum-select-window-5
    "6" 'winum-select-window-6
    "7" 'winum-select-window-7
    "8" 'winum-select-window-8
    "w l" 'evil-window-right
    "w h" 'evil-window-left
    "w j" 'evil-window-down
    "w k" 'evil-window-up
    "w <right>" 'evil-window-right
    "w <left>" 'evil-window-left
    "w <down>" 'evil-window-down
    "w <up>" 'evil-window-up
    "w H" 'evil-window-move-far-left
    "w L" 'evil-window-move-far-right
    "w J" 'evil-window-move-very-bottom
    "w K" 'evil-window-move-very-top
    "w x" 'evil-window-exchange
    "w r" 'evil-window-rotate-downwards
    "w o" 'delete-other-windows
    "jj" 'evil-avy-goto-char
    "jl" 'evil-avy-goto-line
    "jw" 'evil-avy-goto-word-0
    "ji" 'imenu
    "SPC" 'execute-extended-command
    "/" 'counsel-grep-or-swiper
    "TAB" 'evil-switch-to-windows-last-buffer
    "bb" 'consult-buffer
    "bh" 'previous-buffer
    "bl" 'next-buffer
    "bms" 'bookmark-set-no-overwrite
    "bmd" 'bookmark-delete
    "bmr" 'bookmark-rename
    "bml" 'bookmark-bmenu-list
    "bmj" 'bookmark-jump
    "<left>" 'previous-buffer
    "<right>" 'next-buffer
    "bd" 'kill-current-buffer
    "cb" 'copy-to-buffer
    "cg" 'company-gtags
    "el" 'flycheck-list-errors
    "en" 'flycheck-next-error
    "ep" 'flycheck-previous-error
    "ff" 'find-file
    "fr" 'recentf
    "ft" 'dired
    "feD" 'my-open-cfg
    "fp" 'project-find-file
    ;; "gs" 'helm-gtags-find-symbol
    ;; "gS" 'counsel-gtags-find-symbol-other-window
    ;; "gf" 'counsel-gtags-find-file
    ;; "gF" 'counsel-gtags-find-file-other-window
    ;; "gd" 'counsel-gtags-find-definition
    ;; "gD" 'counsel-gtags-find-definition-other-window
    ;; "gr" 'counsel-gtags-find-reference
    ;; "gR" 'counsel-gtags-find-reference-other-window
    "gC" 'helm-gtags-create-tags
    "gd" 'helm-gtags-find-tag
    "gD" 'helm-gtags-find-tag-other-window
    "gf" 'helm-gtags-select-path
    "gG" 'helm-gtags-dwim-other-window
    "gi" 'helm-gtags-tags-in-this-function
    "gl" 'helm-gtags-parse-file
    "gn" 'helm-gtags-next-history
    "gp" 'helm-gtags-previous-history
    "gr" 'helm-gtags-find-rtag
    "gR" 'helm-gtags-resume
    "gs" 'helm-gtags-select
    "gS" 'helm-gtags-show-stack
    "gy" 'helm-gtags-find-symbol
    "gu" 'helm-gtags-update-tags
    "gtn" 'git-gutter:next-hunk
    "gts" 'git-gutter:stage-hunk
    "gtp" 'git-gutter:previous-hunk
    ;; "gu" 'ggtags-update-tags
    "h." 'highlight-symbol
    "hu" 'highlight-symbol-remove-all
    "mgb" 'magit-blame
    "mgc" 'magit-commit
    "mgs" 'magit-status
    "mgd" 'magit-ediff-show-unstaged
    "mgf" 'magit-find-file
    "mgl" 'magit-log-current
    "mgp" 'magit-dispatch
    "wd" 'delete-window
    "wD" 'delete-other-windows
    "w-" 'split-window-below
    "w/" 'split-window-right
    "wm" 'maximize-window
    "w=" 'balance-windows-area
    "w]" 'enlarge-window-horizontally
    "w[" 'shrink-window-horizontally
    "w}" 'enlarge-window
    "w{" 'shrink-window
    ";" 'comment-line
    "qr" 'restart-emacs
    "ss" 'consult-line
    "si" 'swiper-isearch
    "Ts" 'load-theme
    "tl" 'toggle-word-wrap
    "tw" 'my-toggle-trailing-whitespace
    "tt" 'tab-switch
    "tn" 'tab-new
    "td" 'tab-close
    "tD" 'tab-close-other
    "tr" 'tab-rename
    "t TAB" 'tab-recent
    "t <left>" 'tab-previous
    "t <right>" 'tab-next
    "xdw" 'delete-trailing-whitespace
    "en" 'next-error
    "ep" 'previous-error
    "rg" 'rgrep
    )
  )


(use-package which-key
  :ensure t
  :config
  :hook (after-init . which-key-mode)
  )

;; Display the purpose and comment of each command in minibuffer
(use-package marginalia
  :ensure t
  :hook
  (after-init . marginalia-mode)
  )

;; Enable vertico


;(use-package vertico
;  :custom
  ;; (vertico-scroll-margin 0) ;; Different scroll margin
  ;; (vertico-count 20) ;; Show more candidates
  ;; (vertico-resize t) ;; Grow and shrink the Vertico minibuffer
;  (vertico-cycle t) ;; Enable cycling for `vertico-next/previous'
;  :config
;  (vertico-mode 1)
;  )

(use-package ivy
  :ensure t
  :hook
  (after-init . ivy-mode)
  )

(use-package vertico-posframe
 :hook (after-init . vertico-posframe-mode)
 )

;; Optionally use the `orderless' completion style.
(use-package orderless
  :custom
  ;; Configure a custom style dispatcher (see the Consult wiki)
  ;; (orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch))
  ;; (orderless-component-separator #'orderless-escapable-split-on-space)
  (completion-styles '(orderless basic))
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles partial-completion)))))

(use-package consult
  :bind ("C-s" . consult-line))

;; A few more useful configurations...
(use-package emacs
  :custom
  ;; Support opening new minibuffers from inside existing minibuffers.
  (enable-recursive-minibuffers t)
  ;; Emacs 28 and newer: Hide commands in M-x which do not work in the current
  ;; mode.  Vertico commands are hidden in normal buffers. This setting is
  ;; useful beyond Vertico.
  (read-extended-command-predicate #'command-completion-default-include-p)
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
  (defun crm-indicator (args)
    (cons (format "[CRM%s] %s"
                  (replace-regexp-in-string
                   "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
                   crm-separator)
                  (car args))
          (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))

(use-package ivy
  :ensure t
  :diminish ivy-mode
  :hook (after-init . ivy-mode))

(use-package swiper)
(use-package avy)
(use-package helm)
;; (use-package counsel)
;; (global-set-key (kbd "C-s") 'counsel-grep-or-swiper)

(use-package company-box
  :if window-system
  :hook (company-mode . company-box-mode))

(use-package company-c-headers)

(use-package company
  :config
  (setq
   ;; company-dabbrev-code-everywhere t
   company-selection-wrap-around t
   company-dabbrev-code-modes nil
   scompany-dabbrev-code-other-buffers 'all
   company-dabbrev-downcase nil
   company-dabbrev-ignore-case t
   company-dabbrev-other-buffers 'all
   company-tooltip-align-annotations t
   company-require-match nil
   company-minimum-prefix-length 1
   company-show-numbers t
   company-tooltip-limit 100
   company-idle-delay 0
   company-echo-delay 0
   company-tooltip-offset-display 'scrollbar
   company-begin-commands '(self-insert-command)
   )
  ;; (add-to-list 'company-backends 'company-yasnippet)
  :hook (
         (prog-mode . global-company-mode)
         )
  )

(use-package company-statistics
  :hook (global-company-mode . company-statistics-mode)
  )

(use-package highlight-symbol
  :hook (prog-mode . highlight-symbol-mode)
  )

(use-package saveplace
  :ensure t
  :hook (after-init . save-place-mode))

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

(use-package magit)


(use-package git-gutter
  :defer 0.3
  :delight
  :config (global-git-gutter-mode))

(use-package rainbow-delimiters
  :hook ('prog-mode . rainbow-delimiters-mode)
  )

(use-package helm-gtags
  :defer t
  :init
  (progn
    (setq helm-gtags-ignore-case t
          helm-gtags-auto-update t
          helm-gtags-use-input-at-cursor t
          helm-gtags-pulse-at-cursor t)
    ))

(use-package drag-stuff
  :bind (("<M-up>" . drag-stuff-up)
         ("<M-down>" . drag-stuff-down)
         ("<M-left>" . drag-stuff-left)
         ("<M-right>" . drag-stuff-right)))

(use-package flycheck
  :config (setq truncate-lines nil) ;; wrap when to long
  (custom-set-faces
   '(flycheck-error ((t (:foreground "red"))))
   '(flycheck-warning ((t (:foreground "yellow"))))
   '(flycheck-info ((t (:foreground "#61afef"))))
   )
  :hook (after-init . global-flycheck-mode)
  )

(defvar my-clangd-args
  '(
    ;; "-j=8"
    ;; "--all-scopes-completion"
    "--background-index"
    "--clang-tidy"
    "--pch-storage=memory"
    "--header-insertion=never"
    ;; "--completion-style=detailed"
    )
  )

(use-package eglot

  :hook ((prog-mode . (lambda ()
                        (unless (derived-mode-p 'emacs-lisp-mode 'lisp-mode 'makefile-mode 'snippet-mode)
                          (eglot-ensure))))
         ((markdown-mode yaml-mode yaml-ts-mode) . eglot-ensure))
  :init
  (setq eglot-autoshutdown t
        eglot-send-changes-idle-time 0.5)
  :config
  (add-to-list 'eglot-server-programs (cons
                                       '(c++-mode c-mode)
                                       (append '("clangd") my-clangd-args)
                                       )
               )
  (use-package flycheck-eglot
    :config (global-flycheck-eglot-mode 1)
    )
  (use-package consult-eglot)
  (my-space-leader-def
    "bf" 'eglot-format-buffer
    "la" 'eglot-code-actions
    "ld" 'eldoc
    "lr" 'eglot-rename
    "li" 'consult-eglot-symbols
    )
  )

(use-package xclip
  :hook
  (after-init . xclip-mode))


(use-package yasnippet
  :after company
  :ensure t
  :hook
  (prog-mode . yas-minor-mode)
  :config
  ;; (yas-reload-all)
  ;; add company-yasnippet to company-backends
  (defun company-mode/backend-with-yas (backend)
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend) backend (list backend))
              '(:with company-yasnippet))))
  (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends))
  ;; unbind <TAB> completion
  (define-key yas-minor-mode-map [(tab)]        nil)
  (define-key yas-minor-mode-map (kbd "TAB")    nil)
  (define-key yas-minor-mode-map (kbd "<tab>")  nil)
  :bind
  (:map yas-minor-mode-map ("S-<tab>" . yas-expand)))

(use-package yasnippet-snippets
  :after yasnippet
  :config (yasnippet-snippets-initialize))

(use-package ivy-yasnippet
  :after yasnippet
  )

(use-package cmake-mode
  ;; :hook (cmake-mode . lsp-deferred)
  :mode ("CMakeLists\\.txt\\'" "\\.cmake\\'")
  )
(use-package cmake-font-lock
  :hook (cmake-mode . cmake-font-lock-activate))
(use-package cmake-ide)
(use-package eldoc-cmake
  :hook (cmake-mode . eldoc-cmake-enable)
  )
(if (display-graphic-p)
    (use-package eldoc-box
      :after eglot
      ;; :hook (eglot-managed-mode . eldoc-box-hover-at-point-mode)
      )
  )

(defun my-cleanup-gc () "Clean up gc."
       (setq gc-cons-threshold  67108864) ; 64M
       (setq gc-cons-percentage 0.1) ; original value
       (garbage-collect))

(run-with-idle-timer 4 nil #'my-cleanup-gc)

(global-auto-revert-mode 1)
(provide 'init)
;;; init.el ends here

